package vh.sphynx.runtime.interpreter;

import java.lang.reflect.Constructor;
import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;

import vh.sphynx.runtime.ExecutionContext;
import vh.sphynx.runtime.SphynxInterpreter;
import vh.sphynx.runtime.TypeConverter;
import vh.sphynx.runtime.VariableStorage;
import vh.sphynx.sphynx.XExpression;
import vh.sphynx.sphynx.XMultiVariableDeclaration;
import vh.sphynx.sphynx.impl.XAddExpressionImpl;
import vh.sphynx.sphynx.impl.XAssignExpressionImpl;
import vh.sphynx.sphynx.impl.XBoolExpressionImpl;
import vh.sphynx.sphynx.impl.XComparativeExpressionImpl;
import vh.sphynx.sphynx.impl.XConstructionExpressionImpl;
import vh.sphynx.sphynx.impl.XInitObjectExpressionImpl;
import vh.sphynx.sphynx.impl.XMultExpressionImpl;
import vh.sphynx.sphynx.impl.XMultiVariableDeclarationImpl;
import vh.sphynx.sphynx.impl.XParenthesizedExpressionImpl;
import vh.sphynx.sphynx.impl.XUnaryTermImpl;
import vh.sphynx.sphynx.impl.XVariableDeclarationImpl;
import vh.sphynx.util.ReflectionUtil;
import vh.sphynx.util.SphynxInvalidOperator;
import vh.sphynx.util.SphynxNullReferenceExpcetion;
import vh.sphynx.util.SphynxReadonlyException;
import vh.sphynx.util.SphynxRuntimeException;
import vh.sphynx.util.SphynxRuntimeUtil;


public class XBasicOperatorInterpreter {
	
	public static final String NEW_OBJECT_FAIL = "Unable to create instance of type '%s' Cause: %s";

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static Object _evaluate(SphynxInterpreter interpreter, XAssignExpressionImpl assignment, ExecutionContext context) {
		Object left = interpreter.evaluate(assignment.getLeft(), context);
		Object rightVal = interpreter.resolveValue(assignment.getRight(), context); 
		String op = assignment.getOperator();
		if ("+=".equals(op) && left instanceof VariableStorage) {
			Object leftVal = ((VariableStorage)left).getValue();
			if (leftVal instanceof List<?>)
				((List) leftVal).add(rightVal);
			return null;	
		}
		
		return basicAssignOp(context, assignment.getLeft(), assignment.getRight(), left, rightVal, op);
		
		/*if (left instanceof VariableStorage && ((VariableStorage) left).isWritable()) {
			VariableStorage var = (VariableStorage) left;
			//Object right = interpreter.evaluate(assignment.getRight(), context);
			Object right = interpreter.resolveValue(assignment.getRight(), context);
			var.setValue(right);
			return right;
		}
		else 
			throw new SphynxRuntimeException(context, assignment, "Left hand side is unassignable");*/
	}

	public static Object _evaluate(SphynxInterpreter interpreter, XMultExpressionImpl exp, ExecutionContext context) {
		Object left = interpreter.resolveValue(exp.getLeftOperand(), context);
		Object right = interpreter.resolveValue(exp.getRightOperand(), context);
		String op = exp.getOperator();
		if (left instanceof VariableStorage)
			left = ((VariableStorage) left).getValue();
		if (right instanceof VariableStorage)
			right = ((VariableStorage) right).getValue();
 
		return basicOp(context, exp, left, right, op);
	}
	
	public static Object _evaluate(SphynxInterpreter interpreter, XAddExpressionImpl exp, ExecutionContext context) {
		Object left = interpreter.resolveValue(exp.getLeftOperand(), context);
		Object right = interpreter.resolveValue(exp.getRightOperand(), context);
		String op = exp.getOperator();
		if (left instanceof VariableStorage)
			left = ((VariableStorage) left).getValue();
		if (right instanceof VariableStorage)
			right = ((VariableStorage) right).getValue();
 
		return basicOp(context, exp, left, right, op);
	}
	
	public static Object _evaluate(SphynxInterpreter interpreter, XComparativeExpressionImpl exp, ExecutionContext context) {
		Object left = interpreter.resolveValue(exp.getLeftOperand(), context);
		Object right = interpreter.resolveValue(exp.getRightOperand(), context);
		
		if (left instanceof Comparable)
			return compareOp((Comparable<?>)left, right, exp.getOperator());
		
		return compareOp(context, exp, left, right, exp.getOperator());
	}
	
	public static Object _evaluate(SphynxInterpreter interpreter, XBoolExpressionImpl exp, ExecutionContext context) {
		Object left = interpreter.resolveValue(exp.getLeftOperand(), context);
		if (left == null)
			throw new SphynxNullReferenceExpcetion(context, exp.getLeftOperand());
		if (! (left instanceof Boolean))
			throw new SphynxInvalidOperator(context, exp.getLeftOperand(), exp.getOperator(), left);
		Boolean l = (Boolean)left;
		
		switch(exp.getOperator().charAt(0)) {
		case '&': 
			if (l) {
				Object right = interpreter.resolveValue(exp.getRightOperand(), context);
				if (right == null)
					throw new SphynxNullReferenceExpcetion(context, exp.getRightOperand());
				if (! (right instanceof Boolean))
					throw new SphynxInvalidOperator(context, exp, exp.getOperator(), left, right);
				return right;
			}
			else
				return l;
		case '|':
			if (!l) {
				Object right = interpreter.resolveValue(exp.getRightOperand(), context);
				if (right == null)
					throw new SphynxNullReferenceExpcetion(context, exp.getRightOperand());
				if (! (right instanceof Boolean))
					throw new SphynxInvalidOperator(context, exp, exp.getOperator(), left, right);
				return right;
			}
			else
				return l;
		case '~':
			Object right = interpreter.resolveValue(exp.getRightOperand(), context);
			if (right == null)
				throw new SphynxNullReferenceExpcetion(context, exp.getRightOperand());
			if (! (right instanceof Boolean))
				throw new SphynxInvalidOperator(context, exp, exp.getOperator(), left, right);
			return l ^ (Boolean)right;
		}
		return null;
	}

	public static Object _evaluate(SphynxInterpreter interpreter, XUnaryTermImpl exp, ExecutionContext context) {
		Object var = interpreter.evaluate(exp.getTerm(), context);
		Object value = var instanceof VariableStorage ? ((VariableStorage) var).getValue() : var;
		if (value == null)
			throw new SphynxNullReferenceExpcetion(context, exp.getTerm());
		switch(exp.getOperator().charAt(0)) {
		case '-':
			if (!ReflectionUtil.isIntegerNumber(value))
				throw new SphynxInvalidOperator(context, exp, exp.getOperator(), value);
			if (exp.getOperator().length() > 1)  //-- operator
				return ((VariableStorage) var).setValue((Integer)value - 1);
			else
				return -(Integer)value;
		case '!':
			if (!(TypeConverter.canConvertTo(value, Boolean.class)))
				throw new SphynxInvalidOperator(context, exp, exp.getOperator(), value);
			return !(Boolean)TypeConverter.convert(value, Boolean.class);
		case '+':
			if (!ReflectionUtil.isIntegerNumber(value))
				throw new SphynxInvalidOperator(context, exp, exp.getOperator(), value);
			value = (Integer)value + 1;
			if (var instanceof VariableStorage)
				((VariableStorage) var).setValue(value);
			else
				throw new SphynxReadonlyException(context, exp);
			return value;
		}
		return value;
	}
	
	public static Object _evaluate(SphynxInterpreter interpreter, XConstructionExpressionImpl exp, ExecutionContext context) {
		Class<?>  clazz = (Class<?>) interpreter.resolveValue(exp.getType(), context);
		
		if (clazz == null)
			throw new SphynxRuntimeException(context, exp, String.format("Class not found: %s", exp.getType().getClass_()));
		
		EList<XExpression> argExps = exp.getArguments();
		Object[] args = new Object[argExps.size()];
		for (int i = 0; i<args.length; i++)
			args[i] = interpreter.resolveValue(argExps.get(i), context);
		Constructor<?> ctor = SphynxRuntimeUtil.findConstructor(clazz, args);
		
		ExecutionContext evaluatingContext = context;
		if (ctor != null)
			try {
				Object obj = ctor.newInstance(args);
				if (exp.getInitExps().size() > 0) {
					evaluatingContext = context.createChildContext();
					evaluatingContext.setThisObject(obj);
					for (XExpression e: exp.getInitExps())
						interpreter.evaluate(e, evaluatingContext);
				}
				return obj;
			} catch (Exception e) {
				throw new SphynxRuntimeException(evaluatingContext, evaluatingContext.getEvaluatingExpression(), 
						String.format(NEW_OBJECT_FAIL, clazz.getName(), e.getMessage()));
			} 
		throw new SphynxRuntimeException(context, exp, 
				String.format(NEW_OBJECT_FAIL, clazz.getName(), "No appropriate constructor found"));
	}
	
	public static Object _evaluate(SphynxInterpreter interpreter, XInitObjectExpressionImpl exp, ExecutionContext context) {
		Object obj = interpreter.resolveValue(exp.getObject(), context);
		if (obj == null)
			if (!exp.isNullSafe())
				throw new SphynxNullReferenceExpcetion(context, exp);
			else
				return null;
		ExecutionContext childContext = context.createChildContext();
		childContext.setThisObject(obj);
		interpreter.evaluate(exp.getInitExp(), childContext);
		return obj;
	}
	
	public static Object _evaluate(SphynxInterpreter interpreter, XParenthesizedExpressionImpl exp, ExecutionContext context) {
		if (exp.getExp().size() == 1)
			return interpreter.resolveValue(exp.getExp().get(0) , context);
		else {
			Object[] result = new Object[exp.getExp().size()];
			for (int i = 0, n = exp.getExp().size(); i < n; i++)
				result[i] = interpreter.resolveValue(exp.getExp().get(i), context);
			return result;
		}
	}

	public static Object _evaluate(SphynxInterpreter interpreter, XMultiVariableDeclarationImpl varDec, ExecutionContext context) {
		for(XExpression exp: varDec.getExp()) {
			interpreter.evaluate(exp, context);
			//if (varObj instanceof VariableStorage)
			//	((VariableStorage) varObj).setWritable(true);
		}
		return null;
	}
	
	public static Object _evaluate(SphynxInterpreter interpreter, XVariableDeclarationImpl varDec, ExecutionContext context) {
		VariableStorage var = null;
		XVariableDeclarationImpl item = varDec;
		boolean writable = varDec.eContainer() instanceof XMultiVariableDeclaration;
		
		var = context.getVariable(item.getName(), false, false);
		if (var == null) { 
			var = context.allocateVariable(writable? VariableStorage.VARIABLE:  VariableStorage.READONLY, false, item.getName(), item.getValue() != null ? 
					interpreter.resolveValue(item.getValue(), context) : null);
		}
		else
			throw new SphynxRuntimeException(context, item, "There aldready exitsted a variable or constant or argument named '" + varDec.getName() + "'");
		
		return var; // return the last variable
	}

	private static Object compareOp(ExecutionContext context, EObject element, Object left, Object right, String operator) {
		if ("==".equals(operator))
			return left == right;
		if ("!=".equals(operator))
			return left != right;
		throw new SphynxInvalidOperator(context, element, operator, left, right);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static Object compareOp(Comparable left, Object right, String operator) {
		int res = right != null ? left.compareTo(right) : 1;
		
		if (">".equals(operator)) return res > 0;
		if (">=".equals(operator)) return res >= 0;
		if ("<".equals(operator)) return res < 0;
		if ("<=".equals(operator)) return res <= 0;
		if ("!=".equals(operator)) return res != 0;
		if ("==".equals(operator)) return res == 0;
		return null;
	}
	
	public static Object basicAssignOp(ExecutionContext context, EObject leftElement, EObject rightElement, Object left, Object right, String op) {
		if (left instanceof VariableStorage && ((VariableStorage) left).isWritable()) {
			VariableStorage var = (VariableStorage) left;
			//Object right = interpreter.evaluate(assignment.getRight(), context);
			//Object right = interpreter.resolveValue(assignment.getRight(), context);
			if (op.length() == 2) {
				Object leftVal = var.getValue();
				right = basicOp(context, rightElement, leftVal, right, op.substring(0, 1));
			}
			var.setValue(right);
			return right;
		}
		else 
			throw new SphynxRuntimeException(context, leftElement, "Left hand side is unassignable");
	}

	public static Object basicOp(ExecutionContext context, EObject element, Object left, Object right, String op) {		
		if (left == null) {
			left = right;
			right = null;
			if (left == null)
				throw new SphynxInvalidOperator(context, element, op, left, right);
		}
		if (left instanceof Integer)
			return integerOp(context, element, left, right, op);
		if (left instanceof String || left instanceof Character)
			return stringOp(context, element, left, right, op);
		throw new SphynxInvalidOperator(context, element, op, left, right);
	}
	
	public static Object stringOp(ExecutionContext context, EObject element, Object left, Object right, String op) {
		if (left == null)
			return right;
		if (right == null)
			return left;
		if (op.charAt(0) == '+')
			return left.toString() + right.toString();
		else
			throw new SphynxInvalidOperator(context, element, op, left, right);
	}
	
	public static Object integerOp(ExecutionContext context, EObject element, Object left, Object right, String op) {		
		if (!(left instanceof Integer) ||
				 !(right instanceof Integer)) 
			throw new SphynxInvalidOperator(context, element, op, left, right);
		
		Integer i1 = (Integer)left;
		Integer i2 = (Integer)right;
		
		switch (op.charAt(0)) {
		case '+': return i1 + i2;
		case '-': return i1 - i2;
		case '*': return i1 * i2;
		case '/': return i1 / i2;
		case '>': return i1 >> i2;
		case '<': return i1 << i2;
		case '%': return i1 % i2;
		case '&': return i1 & i2;
		case '|': return i1 | i2;
		//case '~': return i1 xor i2;
		}
		return null;
	}
}
